Trò chơi đua xe động vật trong UNITY Engine
- ScrollSnapBase.cs
- Layout /
- AnimalRacing /
- Scripts /
- Assets /
- project /
2 using UnityEngine.Events;
3 using UnityEngine.EventSystems;
4
5 namespace UnityEngine.UI.Extensions
6 {
7 public class ScrollSnapBase : MonoBehaviour, IBeginDragHandler, IDragHandler
8 {
9 internal RectTransform _screensContainer;
10 internal bool _isVertical;
11
12 internal int _screens = 1;
13
14 internal float _scrollStartPosition;
15 internal float _childSize;
16 private float _childPos, _maskSize;
17 internal Vector2 _childAnchorPoint;
18 internal ScrollRect _scroll_rect;
19 internal Vector3 _lerp_target;
20 internal bool _lerp;
21 internal bool _pointerDown = false;
22 internal bool _settled = true;
23 internal Vector3 _startPosition = new Vector3();
24 [Tooltip("The currently active page")]
25 internal int _currentPage;
26 internal int _previousPage;
27 internal int _halfNoVisibleItems;
28 private int _bottomItem, _topItem;
29
30 [Serializable]
31 public class SelectionChangeStartEvent : UnityEvent { }
32 [Serializable]
33 public class SelectionPageChangedEvent : UnityEvent<int> { }
34 [Serializable]
35 public class SelectionChangeEndEvent : UnityEvent<int> { }
36
37 [Tooltip("The screen / page to start the control on\n*Note, this is a 0 indexed array")]
38 [SerializeField]
39 public int StartingScreen = 0;
40
41 [Tooltip("The distance between two pages based on page height, by default pages are next to each other")]
42 [SerializeField]
43 [Range(0, 8)]
44 public float PageStep = 1;
45
46 [Tooltip("The gameobject that contains toggles which suggest pagination. (optional)")]
47 public GameObject Pagination;
48
49 [Tooltip("Button to go to the previous page. (optional)")]
50 public GameObject PrevButton;
51
52 [Tooltip("Button to go to the next page. (optional)")]
53 public GameObject NextButton;
54
55 [Tooltip("Transition speed between pages. (optional)")]
56 public float transitionSpeed = 7.5f;
57
58 [Tooltip("Fast Swipe makes swiping page next / previous (optional)")]
59 public Boolean UseFastSwipe = false;
60
61 [Tooltip("How far swipe has to travel to initiate a page change (optional)")]
62 public int FastSwipeThreshold = 100;
63
64 [Tooltip("Speed at which the ScrollRect will keep scrolling before slowing down and stopping (optional)")]
65 public int SwipeVelocityThreshold = 200;
66
67 [Tooltip("The visible bounds area, controls which items are visible/enabled. *Note Should use a RectMask. (optional)")]
68 public RectTransform MaskArea;
69
70 [Tooltip("Pixel size to buffer arround Mask Area. (optional)")]
71 public float MaskBuffer = 1;
72
73 public int CurrentPage
74 {
75 get
76 {
77 return _currentPage;
78 }
79
80 internal set
81 {
82 if ((value != _currentPage && value >= 0 && value < _screensContainer.childCount) || (value == 0 && _screensContainer.childCount == 0))
83 {
84 _previousPage = _currentPage;
85 _currentPage = value;
86 if(MaskArea) UpdateVisible();
87 if(!_lerp) ScreenChange();
88 OnCurrentScreenChange(_currentPage);
89 }
90 }
91 }
92
93 [Tooltip("(Experimental)\nBy default, child array objects will use the parent transform\nHowever you can disable this for some interesting effects")]
94 public bool UseParentTransform = true;
95
96 [Tooltip("Scroll Snap children. (optional)\nEither place objects in the scene as children OR\nPrefabs in this array, NOT BOTH")]
97 public GameObject[] ChildObjects;
98
99 [SerializeField]
100 [Tooltip("Event fires when a user starts to change the selection")]
101 private SelectionChangeStartEvent m_OnSelectionChangeStartEvent = new SelectionChangeStartEvent();
102 public SelectionChangeStartEvent OnSelectionChangeStartEvent { get { return m_OnSelectionChangeStartEvent; } set { m_OnSelectionChangeStartEvent = value; } }
103
104 [SerializeField]
105 [Tooltip("Event fires as the page changes, while dragging or jumping")]
106 private SelectionPageChangedEvent m_OnSelectionPageChangedEvent = new SelectionPageChangedEvent();
107 public SelectionPageChangedEvent OnSelectionPageChangedEvent { get { return m_OnSelectionPageChangedEvent; } set { m_OnSelectionPageChangedEvent = value; } }
108
109 [SerializeField]
110 [Tooltip("Event fires when the page settles after a user has dragged")]
111 private SelectionChangeEndEvent m_OnSelectionChangeEndEvent = new SelectionChangeEndEvent();
112 public SelectionChangeEndEvent OnSelectionChangeEndEvent { get { return m_OnSelectionChangeEndEvent; } set { m_OnSelectionChangeEndEvent = value; } }
113
114 // Use this for initialization
115 void Awake()
116 {
117 _scroll_rect = gameObject.GetComponent<ScrollRect>();
118
119 if (_scroll_rect.horizontalScrollbar || _scroll_rect.verticalScrollbar)
120 {
121 Debug.LogWarning("Warning, using scrollbars with the Scroll Snap controls is not advised as it causes unpredictable results");
122 }
123
124 if (StartingScreen < 0)
125 {
126 StartingScreen = 0;
127 }
128
129 _screensContainer = _scroll_rect.content;
130 if (ChildObjects != null && ChildObjects.Length > 0)
131 {
132 if (_screensContainer.transform.childCount > 0)
133 {
134 Debug.LogError("ScrollRect Content has children, this is not supported when using managed Child Objects\n Either remove the ScrollRect Content children or clear the ChildObjects array");
135 return;
136 }
137
138 InitialiseChildObjectsFromArray();
139 }
140 else
141 {
142 InitialiseChildObjectsFromScene();
143 }
144
145 if (NextButton)
146 NextButton.GetComponent<Button>().onClick.AddListener(() => { NextScreen(); });
147
148 if (PrevButton)
149 PrevButton.GetComponent<Button>().onClick.AddListener(() => { PreviousScreen(); });
150 }
151
152 internal void InitialiseChildObjectsFromScene()
153 {
154 int childCount = _screensContainer.childCount;
155 ChildObjects = new GameObject[childCount];
156 for (int i = 0; i < childCount; i++)
157 {
158 ChildObjects[i] = _screensContainer.transform.GetChild(i).gameObject;
159 if (MaskArea && ChildObjects[i].activeSelf)
160 {
161 ChildObjects[i].SetActive(false);
162 }
163 }
164 }
165
166 internal void InitialiseChildObjectsFromArray()
167 {
168 int childCount = ChildObjects.Length;
169 RectTransform childRect;
170 GameObject child;
171 for (int i = 0; i < childCount; i++)
172 {
173 child = GameObject.Instantiate(ChildObjects[i]);
174 //Optionally, use original GO transform when initialising, by default will use parent RectTransform position/rotation
175 if (UseParentTransform)
176 {
177 childRect = child.GetComponent<RectTransform>();
178 childRect.rotation = _screensContainer.rotation;
179 childRect.localScale = _screensContainer.localScale;
180 childRect.position = _screensContainer.position;
181 }
182
183 child.transform.SetParent(_screensContainer.transform);
184 ChildObjects[i] = child;
185 if (MaskArea && ChildObjects[i].activeSelf)
186 {
187 ChildObjects[i].SetActive(false);
188 }
189 }
190 }
191
192 internal void UpdateVisible()
193 {
194 //If there are no objects in the scene or a mask, exit
195 if (!MaskArea || ChildObjects == null || ChildObjects.Length < 1 || _screensContainer.childCount < 1)
196 {
197 return;
198 }
199
200 _maskSize = _isVertical ? MaskArea.rect.height : MaskArea.rect.width;
201 _halfNoVisibleItems = (int)Math.Round(_maskSize / (_childSize * MaskBuffer), MidpointRounding.AwayFromZero) / 2;
202 _bottomItem = _topItem = 0;
203 //work out how many items below the current page can be visible
204 for (int i = _halfNoVisibleItems + 1; i > 0; i--)
205 {
206 _bottomItem = _currentPage - i < 0 ? 0 : i;
207 if (_bottomItem > 0) break;
208 }
209
210 //work out how many items above the current page can be visible
211 for (int i = _halfNoVisibleItems + 1; i > 0; i--)
212 {
213 _topItem = _screensContainer.childCount - _currentPage - i < 0 ? 0 : i;
214 if (_topItem > 0) break;
215 }
216
217 //Set the active items active
218 for (int i = CurrentPage - _bottomItem; i < CurrentPage + _topItem; i++)
219 {
220 try
221 {
222 ChildObjects[i].SetActive(true);
223 }
224 catch
225 {
226 Debug.Log("Failed to setactive child [" + i + "]");
227 }
228 }
229
230 //Deactivate items out of visibility at the bottom of the ScrollRect Mask (only on scroll)
231 if (_currentPage > _halfNoVisibleItems) ChildObjects[CurrentPage - _bottomItem].SetActive(false);
232 //Deactivate items out of visibility at the top of the ScrollRect Mask (only on scroll)
233 if (_screensContainer.childCount - _currentPage > _topItem) ChildObjects[CurrentPage + _topItem].SetActive(false);
234 }
235
236 //Function for switching screens with buttons
237 public void NextScreen()
238 {
239 if (_currentPage < _screens - 1)
240 {
241 if (!_lerp) StartScreenChange();
242
243 _lerp = true;
244 CurrentPage = _currentPage + 1;
245 GetPositionforPage(_currentPage, ref _lerp_target);
246 ScreenChange();
247 }
248 }
249
250 //Function for switching screens with buttons
251 public void PreviousScreen()
252 {
253 if (_currentPage > 0)
254 {
255 if (!_lerp) StartScreenChange();
256
257 _lerp = true;
258 CurrentPage = _currentPage - 1;
259 GetPositionforPage(_currentPage, ref _lerp_target);
260 ScreenChange();
261 }
262 }
263
264 /// <summary>
265 /// Function for switching to a specific screen
266 /// *Note, this is based on a 0 starting index - 0 to x
267 /// </summary>
268 /// <param name="screenIndex">0 starting index of page to jump to</param>
269 public void GoToScreen(int screenIndex)
270 {
271 if (screenIndex <= _screens - 1 && screenIndex >= 0)
272 {
273 if (!_lerp) StartScreenChange();
274
275 _lerp = true;
276 CurrentPage = screenIndex;
277 GetPositionforPage(_currentPage, ref _lerp_target);
278 ScreenChange();
279 }
280 }
281
282 /// <summary>
283 /// Gets the closest page for the current Scroll Rect container position
284 /// </summary>
285 /// <param name="pos">Position to test, normally the Scroll Rect container Local position</param>
286 /// <returns>Closest Page number (zero indexed array value)</returns>
287 internal int GetPageforPosition(Vector3 pos)
288 {
289 return _isVertical ?
290 -(int)Math.Round((pos.y - _scrollStartPosition) / _childSize) :
291 -(int)Math.Round((pos.x - _scrollStartPosition) / _childSize);
292 }
293
294 /// <summary>
295 /// Validates if the current Scroll Rect container position is within the bounds for a page
296 /// </summary>
297 /// <param name="pos">Position to test, normally the Scroll Rect container Local position</param>
298 /// <returns>True / False, is the position in the bounds of a page</returns>
299 internal bool IsRectSettledOnaPage(Vector3 pos)
300 {
301 return _isVertical ?
302 -((pos.y - _scrollStartPosition) / _childSize) == -(int)Math.Round((pos.y - _scrollStartPosition) / _childSize) :
303 -((pos.x - _scrollStartPosition) / _childSize) == -(int)Math.Round((pos.x - _scrollStartPosition) / _childSize);
304 }
305
306 /// <summary>
307 /// Returns the local position for a child page based on the required page number
308 /// </summary>
309 /// <param name="page">Page that the position is required for (Zero indexed array value)</param>
310 /// <param name="target">Outputs the local position for the selected page</param>
311 internal void GetPositionforPage(int page, ref Vector3 target)
312 {
313 _childPos = -_childSize * page;
314 if (_isVertical)
315 {
316 target.y = _childPos + _scrollStartPosition;
317 }
318 else
319 {
320 target.x = _childPos + _scrollStartPosition;
321 }
322 }
323
324 /// <summary>
325 /// Updates the _Lerp target to the closest page and updates the pagination bullets. Each control's update loop will then handle the move.
326 /// </summary>
327 internal void ScrollToClosestElement()
328 {
329 _lerp = true;
330 CurrentPage = GetPageforPosition(_screensContainer.localPosition);
331 GetPositionforPage(_currentPage, ref _lerp_target);
332 OnCurrentScreenChange(_currentPage);
333 }
334
335 /// <summary>
336 /// notifies pagination indicator and navigation buttons of a screen change
337 /// </summary>
338 internal void OnCurrentScreenChange(int currentScreen)
339 {
340 ChangeBulletsInfo(currentScreen);
341 ToggleNavigationButtons(currentScreen);
342 }
343
344 /// <summary>
345 /// changes the bullets on the bottom of the page - pagination
346 /// </summary>
347 /// <param name="targetScreen"></param>
348 private void ChangeBulletsInfo(int targetScreen)
349 {
350 if (Pagination)
351 for (int i = 0; i < Pagination.transform.childCount; i++)
352 {
353 Pagination.transform.GetChild(i).GetComponent<Toggle>().isOn = (targetScreen == i)
354 ? true
355 : false;
356 }
357 }
358
359 /// <summary>
360 /// disables the page navigation buttons when at the first or last screen
361 /// </summary>
362 /// <param name="targetScreen"></param>
363 private void ToggleNavigationButtons(int targetScreen) {
364 if (PrevButton) {
365 PrevButton.GetComponent<Button>().interactable = targetScreen > 0;
366 }
367
368 if (NextButton) {
369 NextButton.GetComponent<Button>().interactable = targetScreen < _screensContainer.transform.childCount - 1;
370 }
371 }
372
373 private void OnValidate()
374 {
375 var children = gameObject.GetComponent<ScrollRect>().content.childCount;
376 if (children != 0 || ChildObjects != null)
377 {
378 var childCount = ChildObjects == null || ChildObjects.Length == 0 ? children : ChildObjects.Length;
379 if (StartingScreen > childCount - 1)
380 {
381 StartingScreen = childCount - 1;
382 }
383
384 if (StartingScreen < 0)
385 {
386 StartingScreen = 0;
387 }
388 }
389
390 if (MaskBuffer <= 0)
391 {
392 MaskBuffer = 1;
393 }
394
395 if (PageStep < 0)
396 {
397 PageStep = 0;
398 }
399
400 if (PageStep > 8)
401 {
402 PageStep = 9;
403 }
404 }
405
406 /// <summary>
407 /// Event fires when the user starts to change the page, either via swipe or button.
408 /// </summary>
409 internal void StartScreenChange()
410 {
411 OnSelectionChangeStartEvent.Invoke();
412 }
413
414 /// <summary>
415 /// Event fires when the currently viewed page changes, also updates while the scroll is moving
416 /// </summary>
417 internal void ScreenChange()
418 {
419 OnSelectionPageChangedEvent.Invoke(_currentPage);
420 }
421
422 /// <summary>
423 /// Event fires when control settles on a page, outputs the new page number
424 /// </summary>
425 internal void EndScreenChange()
426 {
427 OnSelectionChangeEndEvent.Invoke(_currentPage);
428 _settled = true;
429 }
430
431 #region Interfaces
432 /// <summary>
433 /// Touch screen to start swiping
434 /// </summary>
435 /// <param name="eventData"></param>
436 public void OnBeginDrag(PointerEventData eventData)
437 {
438 _pointerDown = true;
439 _settled = false;
440 StartScreenChange();
441 _startPosition = _screensContainer.localPosition;
442 }
443
444 /// <summary>
445 /// While dragging do
446 /// </summary>
447 /// <param name="eventData"></param>
448 public void OnDrag(PointerEventData eventData)
449 {
450 _lerp = false;
451 }
452
453 #endregion
454 }
455 }
Use this for initialization
Optionally, use original GO transform when initialising, by default will use parent RectTransform positionrotation
If there are no objects in the scene or a mask, exit
work out how many items below the current page can be visible
work out how many items above the current page can be visible
Set the active items active
Deactivate items out of visibility at the bottom of the ScrollRect Mask (only on scroll)
Deactivate items out of visibility at the top of the ScrollRect Mask (only on scroll)
Function for switching screens with buttons
Function for switching screens with buttons
Function for switching to a specific screen
*Note, this is based on a 0 starting index - 0 to x
0 starting index of page to jump to
Gets the closest page for the current Scroll Rect container position
Position to test, normally the Scroll Rect container Local position
Validates if the current Scroll Rect container position is within the bounds for a page
Position to test, normally the Scroll Rect container Local position
Returns the local position for a child page based on the required page number
Page that the position is required for (Zero indexed array value)
Outputs the local position for the selected page
Updates the _Lerp target to the closest page and updates the pagination bullets. Each control's update loop will then handle the move.
notifies pagination indicator and navigation buttons of a screen change
changes the bullets on the bottom of the page - pagination
disables the page navigation buttons when at the first or last screen
Event fires when the user starts to change the page, either via swipe or button.
Event fires when the currently viewed page changes, also updates while the scroll is moving
Event fires when control settles on a page, outputs the new page number
Touch screen to start swiping
While dragging do